﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *
 **/

using System;
using System.IO;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Peter.Common;
using Peter.Common.MainMenu;
using Peter.Data;

namespace Peter.Logic
{
   /// <summary>
   /// The model for the in house image viewer.
   /// </summary>
   public sealed class ModelImageViewer : PeterDocument
   {
      private readonly BitmapMetadata m_Metadata;
      private RelayCommand m_CmdStretch;
      private Stretch m_ImageStretch;

      /// <summary>
      /// Initializes a new image viewer.
      /// </summary>
      /// <param name="file">The file info object to open.</param>
      public ModelImageViewer (FileInfo file)
      {
         this.FileInfo = file;
         this.Title = file.Name;
         this.ToolTip = file.FullName;
         if (this.FileInfo.Exists)
         {
            this.FilePath = file.FullName;
            this.Image = BitmapFrame.Create (new Uri (file.FullName), BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
            this.m_Metadata = (BitmapMetadata) this.Image.Metadata;
         }
         this.Icon = MainMenuIcon.Picture;
         this.ContentId = string.Format ("{0}|{1}", Guids.IMAGE_VIEWER, this.FilePath);
      }

      /// <summary>
      /// Gets the path to the file
      /// </summary>
      public string FilePath { get; private set; }

      /// <summary>
      /// Gets the image to display.
      /// </summary>
      public ImageSource Image { get; private set; }

      /// <summary>
      /// Gets the text of the document.
      /// </summary>
      public override string Text
      {
         get { return string.Empty; }
      }

      /// <summary>
      /// Gets the stretch command.
      /// </summary>
      public ICommand StretchCommand
      {
         get { return this.m_CmdStretch ?? (this.m_CmdStretch = new RelayCommand (this.OnStrech)); }
      }

      /// <summary>
      /// Gets or Set how the image should be stretched.
      /// </summary>
      public Stretch ImageStretch
      {
         get { return this.m_ImageStretch; }
         set
         {
            this.m_ImageStretch = value;
            this.RaisePropertyChanged ("ImageStretch");
            this.RaisePropertyChanged ("Image");
         }
      }

      /// <summary>
      /// Gets the images width.
      /// </summary>
      public uint? Width
      {
         get
         {
            return Convert.ToUInt32 (QueryMetadata ("/app1/ifd/exif/subifd:{uint=40962}") ?? this.Image.Width);
         }
      }

      /// <summary>
      /// Gets the images height.
      /// </summary>
      public uint? Height
      {
         get
         {
            return Convert.ToUInt32 (QueryMetadata ("/app1/ifd/exif/subifd:{uint=40963}") ?? this.Image.Height);
         }
      }

      /// <summary>
      /// Gets the images horizontal resolution.
      /// </summary>
      public decimal? HorizontalResolution
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif:{uint=282}");
            if (val != null)
               return ParseUnsignedRational ((ulong)val);
            return null;
         }
      }

      /// <summary>
      /// Gets the images vertial resolution.
      /// </summary>
      public decimal? VerticalResolution
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif:{uint=283}");
            if (val != null)
               return ParseUnsignedRational ((ulong)val);
            return null;
         }
      }

      /// <summary>
      /// Gets the manufacturer of the camera the image was taken with.
      /// </summary>
      public string EquipmentManufacturer
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif:{uint=271}");
            return (val != null ? (string)val : "None");
         }
      }

      /// <summary>
      /// Gets the camera the image was taken with.
      /// </summary>
      public string CameraModel
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif:{uint=272}");
            return (val != null ? (string)val : "None");
         }
      }

      /// <summary>
      /// Gets the software the image was created with.
      /// </summary>
      public string CreationSoftware
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif:{uint=305}");
            return (val != null ? (string)val : "None");
         }
      }

      public ColorRepresentation ColorRepresentation
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif/subifd:{uint=40961}");
            if (val == null)
               return ColorRepresentation.Uncalibrated;
            return (ushort)val == 1
               ? ColorRepresentation.sRGB
               : ColorRepresentation.Uncalibrated;
         }
      }

      public decimal? ExposureTime
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif/subifd:{uint=33434}");
            if (val != null)
               return ParseUnsignedRational ((ulong)val);
            return null;
         }
      }

      public decimal? ExposureCompensation
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif/subifd:{uint=37380}");
            if (val != null)
               return ParseSignedRational ((long)val);
            return null;
         }
      }

      public decimal? LensAperture
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif/subifd:{uint=33437}");
            if (val != null)
               return ParseUnsignedRational ((ulong)val);
            return null;
         }
      }

      public decimal? FocalLength
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif/subifd:{uint=37386}");
            if (val != null)
               return ParseUnsignedRational ((ulong)val);
            return null;
         }
      }

      public ushort? IsoSpeed
      {
         get
         {
            return (ushort?)QueryMetadata ("/app1/ifd/exif/subifd:{uint=34855}");
         }
      }

      public FlashMode FlashMode
      {
         get
         {
            return (ushort)QueryMetadata ("/app1/ifd/exif/subifd:{uint=37385}") % 2 == 1
               ? FlashMode.FlashFired
               : FlashMode.FlashDidNotFire;
         }
      }

      public ExposureMode ExposureMode
      {
         get
         {
            var mode = (ushort?)QueryMetadata ("/app1/ifd/exif/subifd:{uint=34850}");

            if (mode == null)
               return ExposureMode.Unknown;
            switch ((int)mode)
            {
               case 1: return ExposureMode.Manual;
               case 2: return ExposureMode.NormalProgram;
               case 3: return ExposureMode.AperturePriority;
               case 4: return ExposureMode.ShutterPriority;
               case 5: return ExposureMode.LowSpeedMode;
               case 6: return ExposureMode.HighSpeedMode;
               case 7: return ExposureMode.PortraitMode;
               case 8: return ExposureMode.LandscapeMode;
               default: return ExposureMode.Unknown;
            }
         }
      }

      public WhiteBalanceMode WhiteBalanceMode
      {
         get
         {
            var mode = (ushort?)QueryMetadata ("/app1/ifd/exif/subifd:{uint=37384}");

            if (mode == null)
               return WhiteBalanceMode.Unknown;
            switch ((int)mode)
            {
               case 1: return WhiteBalanceMode.Daylight;
               case 2: return WhiteBalanceMode.Fluorescent;
               case 3: return WhiteBalanceMode.Tungsten;
               case 10: return WhiteBalanceMode.Flash;
               case 17: return WhiteBalanceMode.StandardLightA;
               case 18: return WhiteBalanceMode.StandardLightB;
               case 19: return WhiteBalanceMode.StandardLightC;
               case 20: return WhiteBalanceMode.D55;
               case 21: return WhiteBalanceMode.D65;
               case 22: return WhiteBalanceMode.D75;
               case 255: return WhiteBalanceMode.Other;
               default: return WhiteBalanceMode.Unknown;
            }
         }
      }

      public DateTime? DateImageTaken
      {
         get
         {
            var val = QueryMetadata ("/app1/ifd/exif/subifd:{uint=36867}");
            if (val == null)
               return this.FileInfo.CreationTime;
            var date = (string)val;
            try
            {
               return new DateTime (
                  int.Parse (date.Substring (0, 4)),    // year
                  int.Parse (date.Substring (5, 2)),    // month
                  int.Parse (date.Substring (8, 2)),    // day
                  int.Parse (date.Substring (11, 2)),   // hour
                  int.Parse (date.Substring (14, 2)),   // minute
                  int.Parse (date.Substring (17, 2))    // second
                  );
            }
            catch (FormatException)
            {
               return null;
            }
            catch (OverflowException)
            {
               return null;
            }
            catch (ArgumentNullException)
            {
               return null;
            }
            catch (NullReferenceException)
            {
               return null;
            }
         }

      }

      /// <summary>
      /// Occurs when the stretch command is triggered.
      /// </summary>
      /// <param name="obj">Stretch command parameter.</param>
      private void OnStrech (object obj)
      {
         var stretch = obj.ToString ().ToLower ();
         if (stretch == "actual")
            this.ImageStretch = Stretch.None;
         else if (stretch == "fit")
            this.ImageStretch = Stretch.Uniform;
         else if (stretch == "fill")
            this.ImageStretch = Stretch.Fill;
      }

      private static decimal ParseUnsignedRational (ulong exifValue)
      {
         return (exifValue & 0xFFFFFFFFL) / (decimal)((exifValue & 0xFFFFFFFF00000000L) >> 32);
      }

      private static decimal ParseSignedRational (long exifValue)
      {
         return (exifValue & 0xFFFFFFFFL) / (decimal)((exifValue & 0x7FFFFFFF00000000L) >> 32);
      }

      private object QueryMetadata (string query)
      {
         if (this.m_Metadata == null) return null;
         return this.m_Metadata.ContainsQuery (query) ? this.m_Metadata.GetQuery (query) : null;
      }
   }

   public enum ColorRepresentation
   {
      sRGB,
      Uncalibrated
   }

   public enum FlashMode
   {
      FlashFired,
      FlashDidNotFire
   }

   public enum ExposureMode
   {
      Manual,
      NormalProgram,
      AperturePriority,
      ShutterPriority,
      LowSpeedMode,
      HighSpeedMode,
      PortraitMode,
      LandscapeMode,
      Unknown
   }

   public enum WhiteBalanceMode
   {
      Daylight,
      Fluorescent,
      Tungsten,
      Flash,
      StandardLightA,
      StandardLightB,
      StandardLightC,
      D55,
      D65,
      D75,
      Other,
      Unknown
   }
}
